/*
 * Copyright 2011 derek.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.aocore.awesomo;

import com.aocore.awesomo.api.Task;
import com.aocore.awesomo.spi.Plugin;
import com.aocore.packet.CCPacket;
import com.aocore.packet.CSPacket;
import com.aocore.packet.GCPacket;
import com.aocore.packet.GSPacket;
import com.aocore.packet.Packet;
import com.aocore.packet.RCPacket;
import com.aocore.packet.RSPacket;
import com.aocore.proxy.ChatProxy;
import com.aocore.proxy.GameProxy;
import com.aocore.proxy.RealmProxy;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.ServiceLoader;

/**
 *
 * @author derek
 */
public class AwesomO extends Bot
{
    private boolean shutdown = false;
    private final ServiceLoader<Plugin> plugins;
    private final List eventHandlers = new ArrayList();
    private final Deque<Task> lowPriorityStack = new ArrayDeque<Task>();
    private final Deque<Task> mediumPriorityStack = new ArrayDeque<Task>();
    private final Deque<Task> highPriorityStack = new ArrayDeque<Task>();

    public AwesomO()
    {
        plugins = ServiceLoader.load(Plugin.class);

        for (Plugin plugin : plugins)
        {
            System.out.println("Loading Plugin: " + plugin);
        }
    }

    private InetSocketAddress realmAddress;
    private InetSocketAddress gameAddress;

    private ChatProxy chatProxy;
    private RealmProxy realmProxy;
    private GameProxy gameProxy;

    public InetSocketAddress getRealmAddress()
    {
        return realmAddress;
    }

    public void setRealmAddress(InetSocketAddress realmAddress)
    {
        this.realmAddress = realmAddress;
    }

    public InetSocketAddress getGameAddress()
    {
        return gameAddress;
    }

    public void setGameAddress(InetSocketAddress gameAddress)
    {
        this.gameAddress = gameAddress;
    }

    public void setChatProxy(ChatProxy chatProxy)
    {
        this.chatProxy = chatProxy;
    }

    public void setRealmProxy(RealmProxy realmProxy)
    {
        this.realmProxy = realmProxy;
    }

    public void setGameProxy(GameProxy gameProxy)
    {
        this.gameProxy = gameProxy;
    }

    public Packet processPacket(Packet packet)
    {
        try
        {
            Method method = Bot.class.getDeclaredMethod("process", packet.getClass());
            return (Packet) method.invoke(this, packet);
        }
        catch (NoSuchMethodException e)
        {
            //e.printStackTrace(System.err);
        }
        catch (IllegalAccessException e)
        {
            e.printStackTrace(System.err);
        }
        catch (IllegalArgumentException e)
        {
            e.printStackTrace(System.err);
        }
        catch (InvocationTargetException e)
        {
            e.printStackTrace(System.err);
        }
        return packet;
    }

    @Override
    public void sendPacket(CSPacket packet) throws IOException
    {
        chatProxy.writePacket(packet);
    }

    @Override
    public void sendPacket(CCPacket packet) throws IOException
    {
        chatProxy.writePacket(packet);
    }

    @Override
    public void sendPacket(RSPacket packet) throws IOException
    {
        realmProxy.writePacket(packet);
    }

    @Override
    public void sendPacket(RCPacket packet) throws IOException
    {
        realmProxy.writePacket(packet);
    }

    @Override
    public void sendPacket(GSPacket packet) throws IOException
    {
        gameProxy.writePacket(packet);
    }

    @Override
    public void sendPacket(GCPacket packet) throws IOException
    {
        gameProxy.writePacket(packet);
    }

    private void processStack(Deque<Task> stack) throws IOException
    {
        Task currentTask = stack.peek();
        Task nextTask = currentTask.run(this);

        if (nextTask == null)
        {
            stack.pop();
        }
        else if (currentTask != nextTask)
        {
            stack.push(nextTask);
        }
    }

    @Override
    public final void run()
    {
        try
        {
            for (Plugin plugin : plugins)
            {
                plugin.registerEvents(this);
            }

            System.out.println("BOT RUNNING");

            while (!shutdown)
            {
                try
                {
                    if (!highPriorityStack.isEmpty())
                    {
                        processStack(highPriorityStack);
                    }
                    else if (!mediumPriorityStack.isEmpty())
                    {
                        processStack(mediumPriorityStack);
                    }
                    else if (!lowPriorityStack.isEmpty())
                    {
                        processStack(lowPriorityStack);
                    }

                    Thread.sleep(10);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace(System.err);
                    highPriorityStack.clear();
                    mediumPriorityStack.clear();
                    lowPriorityStack.clear();
                }
            }
        }
        catch (IOException e)
        {
            e.printStackTrace(System.err);
        }

        System.out.println("BOT STOPPED");
    }

    @Override
    protected void fireEvents(Class event, Object... args)
    {
        for (Object eh : eventHandlers)
        {
            try
            {
                Object eventHandler = eh instanceof Class ? ((Class)eh).newInstance() : eh;

                if (event.isAssignableFrom(eventHandler.getClass()))
                {
                    for (Method method : event.getMethods())
                    {
                        Task task = (Task) method.invoke(eventHandler, args);

                        if (task != null)
                        {
                            switch (task.taskPriority)
                            {
                                case LOW:
                                {
                                    lowPriorityStack.push(task);
                                    break;
                                }
                                case MEDIUM:
                                {
                                    mediumPriorityStack.push(task);
                                    break;
                                }
                                case HIGH:
                                {
                                    highPriorityStack.push(task);
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            catch (InstantiationException e)
            {
                e.printStackTrace(System.err);
            }
            catch (IllegalAccessException e)
            {
                e.printStackTrace(System.err);
            }
            catch (IllegalArgumentException e)
            {
                e.printStackTrace(System.err);
            }
            catch (InvocationTargetException e)
            {
                e.printStackTrace(System.err);
            }
        }
    }

    @Override
    public void registerEvent(Object event)
    {
        eventHandlers.add(event);
    }

    @Override
    public void unregisterEvent(Object event)
    {
        eventHandlers.remove(event);
    }

    @Override
    public void registerEvent(Class event)
    {
        eventHandlers.add(event);
    }

    @Override
    public void unregisterEvent(Class event)
    {
        eventHandlers.remove(event);
    }
}
